<template>
	<e-popup :show="props.show" @close="closeHandler" :round="props.round">
		<view class="e-picker">

			<slot name="toolbar" >
				<toolbar :show="props.showToolbar" :cancelColor="props.cancelColor" :confirmColor="props.confirmColor"
					:cancelText="props.cancelText" :confirmText="props.confirmText" :title="props.title"
					@cancel="cancel" @confirm="confirm" ></toolbar>
			</slot>

			<picker-view class="e-picker__view" :indicatorStyle="`height: ${addUnit(props.itemHeight)}`"
				:value="innerIndex" :immediateChange="props.immediateChange" :style="{
					height: `${addUnit(Number(props.visibleItemCount) * Number(props.itemHeight))}`
				}" @change="changeHandler">
				<picker-view-column v-for="(item, index) in innerColumns" :key="index" class="e-picker__view__column">
					<text v-if="isArray(item)" class="e-picker__view__column__item" v-for="(item1, index1) in item"
						:key="index1" :style="[
							itemStyle,
							{
								fontWeight: index1 === innerIndex[index] ? 'bold' : 'normal'
							}
						]">
						{{ getItemText(item1) }}
					</text>
				</picker-view-column>
			</picker-view>
			<view v-if="props.loading" class="e-picker--loading">
				<e-loading-icon mode="circle"></e-loading-icon>
			</view>
			<slot name="footer">
			</slot>
		</view>
	</e-popup>
</template>
<script lang="ts">
	import {defineComponent} from 'vue'
	import mpMixin from '../../libs/mixin/mpMixin'
	/**
	 * e-picker
	 * @description 选择器
	 * @property {Boolean}			show				是否显示picker弹窗（默认 false ）
	 * @property {Boolean}			showToolbar			是否显示顶部的操作栏（默认 true ）
	 * @property {Array}			columns				对象数组，设置每一列的数据
	 * @property {Boolean}			loading				是否显示加载中状态（默认 false ）
	 * @property {String | Number}	itemHeight			各列中，单个选项的高度（默认 44 ）
	 * @property {String}			title				顶部标题
	 * @property {String}			cancelText			取消按钮的文字（默认 '取消' ）
	 * @property {String}			confirmText			确认按钮的文字（默认 '确定' ）
	 * @property {String}			cancelColor			取消按钮的颜色（默认 '#909193' ）
	 * @property {String}			confirmColor		确认按钮的颜色（默认 '#3c9cff' ）
	 * @property {String | Number}	visibleItemCount	每列中可见选项的数量（默认 5 ）
	 * @property {String}			keyName				选项对象中，需要展示的属性键名（默认 'text' ）
	 * @property {Boolean}			closeOnClickOverlay	是否允许点击遮罩关闭选择器（默认 false ）
	 * @property {Array}			defaultIndex		各列的默认索引
	 * @property {Boolean}			immediateChange		是否在手指松开时立即触发change事件（默认 false ）
	 * @property {String | Number}	round				圆角值
	 * @event {Function} close		关闭选择器时触发
	 * @event {Function} cancel		点击取消按钮触发
	 * @event {Function} change		当选择值变化时触发
	 * @event {Function} confirm	点击确定按钮，返回当前选择的值
	 */
	export default defineComponent({
		name: 'e-picker',
		mixins: [mpMixin]
	})
</script>
<script lang="ts" setup>
	import {
		ref,
		watch,
		computed,
		toRefs
	} from 'vue'
	import {
		pickerProps,
		pickerEmits
	} from './props';
	import {
		useDefaultProps
	} from '../../libs/hooks'
	import {
		isObj,
		isArray,
		arrayDeepClone,
		sleep,
		getProperty,
		arrayUnion
	} from '../../libs/utils'
	import {
		addUnit
	} from '../../libs/utils/_utils'
	import type {
		PickerContext
	} from '../../libs/tokens'
	import componentsProps, {
		type DefaultProps
	} from "../../libs/config/componentsProps";
	import toolbar from '../toolbar/index.vue'

	const {
		picker: pickerDefaultProps
	} = toRefs(componentsProps);

	const _props = defineProps(pickerProps)

	const props = useDefaultProps < DefaultProps['picker'] > (_props, pickerDefaultProps);
	const emit = defineEmits(pickerEmits)

	// 索引值 ，对应picker-view的value
	const innerIndex = ref < number[] > ([])
	// 各列的值
	const innerColumns = ref < any[] > ([])

	// 上一次选择的列索引
	let lastIndex: number[] = []
	// 上一次的变化列索引
	let columnIndex = 0

	const itemStyle = computed(() => ({
		height: addUnit(props.value.itemHeight),
		lineHeight: addUnit(props.value.itemHeight),
	}))

	// 获取item需要显示的文字，判别为对象还是文本
	const getItemText = (item: Record < string, any > ) => {
		if (isObj(item)) {
			return getProperty(item, props.value.keyName)
		} else {
			return item
		}
	}
	// 关闭选择器
	const closeHandler = () => {
		if (props.value.closeOnClickOverlay) {
			emit('close')
		}
	}
	// 点击工具栏的取消按钮
	const cancel = () => {
		emit('cancel')
	}
	// 点击工具栏的确定按钮
	const confirm = () => {
		emit('confirm', {
			indexs: innerIndex.value,
			value: innerColumns.value.map((item: {
				[x: string]: any;
			}, index: number) => item[innerIndex.value[index]]),
			values: innerColumns.value
		})
	}
	// 选择器某一列的数据发生变化时触发
	const changeHandler = (e: {
		detail: {
			value: any;
		};
	}) => {
		const {
			value
		} = e.detail
		let index = 0,
			_columnIndex = 0
		// 通过对比前后两次的列索引，得出当前变化的是哪一列
		for (let i = 0; i < value.length; i++) {
			let item = value[i]
			if (item !== (lastIndex[i] || 0)) { // 把undefined转为合法假值0
				// 设置_columnIndex为当前变化列的索引
				_columnIndex = i
				// index则为变化列中的变化项的索引
				index = item
				break // 终止循环，即使少一次循环，也是性能的提升
			}
		}
		columnIndex = _columnIndex
		const values = innerColumns.value
		// 将当前的各项变化索引，设置为"上一次"的索引变化值
		setLastIndex(value)
		setIndexs(value)
		emit('change', {
			// #ifndef MP-WEIXIN || MP-LARK
			// 微信小程序不能传递this，会因为循环引用而报错
			// picker: this,
			// #endif
			index,
			indexs: value,
			value: innerColumns.value.map((item: {
				[x: string]: any;
			}, index: string | number) => item[value[index]]),
			// values为当前变化列的数组内容
			values,
			columnIndex
		})
	}
	// 设置index索引，此方法可被外部调用设置
	const setIndexs: PickerContext['setIndexs'] = (index, isSetLastIndex) => {
		innerIndex.value = arrayDeepClone(index)
		if (isSetLastIndex) {
			setLastIndex(index)
		}
	}
	// 记录上一次的各列索引位置
	const setLastIndex = (index: number[]) => {
		// 当能进入此方法，意味着当前设置的各列默认索引，即为“上一次”的选中值，需要记录，是因为changeHandler中
		// 需要拿前后的变化值进行对比，得出当前发生改变的是哪一列
		lastIndex = arrayDeepClone(index)
	}
	// 设置对应列选项的所有值
	const setColumnValues: PickerContext['setColumnValues'] = (_columnIndex, values) => {
		// 替换innerColumns数组中columnIndex索引的值为values，使用的是数组的splice方法
		innerColumns.value.splice(_columnIndex, 1, values)
		// 拷贝一份原有的innerIndex做临时变量，将大于当前变化列的所有的列的默认索引设置为0
		let tmpIndex = arrayDeepClone(innerIndex.value)
		for (let i = 0; i < innerColumns.value.length; i++) {
			if (i > columnIndex) {
				tmpIndex[i] = 0
			}
		}
		// 一次性赋值，不能单个修改，否则无效
		setIndexs(tmpIndex)
	}
	// 获取对应列的所有选项
	const getColumnValues: PickerContext['getColumnValues'] = (columnIndex) => {
		// 进行同步阻塞，因为外部得到change事件之后，可能需要执行setColumnValues更新列的值
		// 索引如果在外部change的回调中调用getColumnValues的话，可能无法得到变更后的列值，这里进行一定延时，保证值的准确性
		(async () => {
			await sleep()
		})()
		return innerColumns.value[columnIndex]
	}
	// 设置整体各列的columns的值
	const setColumns = (columns: any[]) => {

		innerColumns.value = arrayDeepClone(columns)
		// 如果在设置各列数据时，没有被设置默认的各列索引defaultIndex，那么用0去填充它，数组长度为列的数量
		if (innerIndex.value.length === 0) {
			innerIndex.value = props.value.defaultIndex.length ? arrayDeepClone(props.value.defaultIndex) : new Array(columns.length).fill(0)
		}
	}
	// 获取各列选中值对应的索引
	const getIndexs: PickerContext['getIndexs'] = () => {
		return innerIndex.value
	}
	// 获取各列选中的值
	const getValues: PickerContext['getValues'] = () => {
		// 进行同步阻塞，因为外部得到change事件之后，可能需要执行setColumnValues更新列的值
		// 索引如果在外部change的回调中调用getValues的话，可能无法得到变更后的列值，这里进行一定延时，保证值的准确性
		(async () => {
			await sleep()
		})()
		return innerColumns.value.map((item: {
			[x: string]: any;
		}, index: number) => item[innerIndex.value[index]])
	}

	watch(() => props.value.defaultIndex, (v,old) => {
		// 首次或新旧值不相同时更新
		if(!old||arrayUnion(v,old).length !== v.length){
			setIndexs((v as number[]), true)
		}
		
	}, {
		immediate: true,
	})
	watch(() => props.value.columns, (v) => {
		setColumns((v as any[]))
	}, {
		immediate: true,
	})

	defineExpose({
		setIndexs,
		getIndexs,
		getValues,
		setColumnValues,
		getColumnValues,
		confirm
	})
</script>

<style lang="scss" scoped>
	@import '../../libs/theme/picker.scss';
</style>
